// ========================================================================
// Copyright 2008-2009 NEXCOM Systems
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at 
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package org.cipango.ims.hss.auth;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.DHPrivateKeySpec;
import javax.crypto.spec.DHPublicKeySpec;

import org.apache.log4j.Logger;
import org.cipango.diameter.AVP;
import org.cipango.diameter.AVPList;
import org.cipango.diameter.ims.Cx;
import org.cipango.ims.AuthenticationScheme;
import org.cipango.littleims.util.HexString;



public class OneWayAuthenticationVector implements AuthenticationVector {

	private static final Logger __log = Logger.getLogger(OneWayAuthenticationVector.class);
	
	protected static byte[] __amf = { 0, 0};

	private byte[] _sipAuthenticate;
	private byte[] _rand;
	private byte[] _xres;
	private byte[] _ck;
	private byte[] _ik;
	private byte[] _opC;
	private String scheme;
	private BigInteger P;
	private BigInteger G;
	private BigInteger Y;

	
	public OneWayAuthenticationVector(byte[] k, byte[] sqn, byte[] random, byte[] operatorId, String scheme, String p, String g, String y) throws InvalidKeyException, ArrayIndexOutOfBoundsException, UnsupportedEncodingException
	{
		try{
		//Recuperer les parametres necessaires a Diffie Hellman
		this.P = new BigInteger(p);
		this.G = new BigInteger(g);
		this.Y = new BigInteger(y);
		DHParameterSpec parametres = new DHParameterSpec(P, G);

		//Generer les cles publiques et prive du S-CSCF
		KeyPairGenerator generateur = KeyPairGenerator.getInstance("DH");
		generateur.initialize(parametres);
		KeyPair cles = generateur.generateKeyPair();

		//Generer la cle publique du P-CSCF
		KeyFactory kfactory = KeyFactory.getInstance("DiffieHellman");
		DHPublicKeySpec beta = new DHPublicKeySpec(Y, P, G);
		PublicKey pub = kfactory.generatePublic(beta);

		KeyAgreement ka= KeyAgreement.getInstance("DH");
		ka.init(cles.getPrivate());
		ka.doPhase(pub, true);

		//Generer la cle publique du SCSCF a envoyer
		DHPublicKeySpec kspec = (DHPublicKeySpec) kfactory.getKeySpec(cles.getPublic(),
				DHPublicKeySpec.class);
		BigInteger Y = kspec.getY();
		String pubClient = "";
		pubClient = pubClient + Y;
		SecretKey kDH = ka.generateSecret("DES");
		
		// Calculs des parametres d'authentification
		this.scheme = scheme;
		_rand = random;		
		_opC = Milenage.computeOpC(k, operatorId);
		byte[] mac = Milenage.f1(k, _rand, _opC, sqn, __amf);
		byte[] _xresA = Milenage.f2(k, _rand, _opC);
		byte[] _ckA = Milenage.f3(k, _rand, _opC);
		byte[] _ikA = Milenage.f4(k, _rand, _opC);
		byte[] ak = Milenage.f5(k, _rand, _opC);
		byte[] _sipAuthenticateA = computeSipAuthenticate(sqn, __amf, mac, ak);
		
		//Chiffrement des parametres CK, IK , AUTN et XRES avec DH
		_ck = encrypt(_ckA,kDH);
		_ik = encrypt(_ikA,kDH);
		_xres = encrypt(_xresA,kDH);
		byte[] _sipAuthenticateB = encrypt(_sipAuthenticateA,kDH);
		
		// Concatenation de l'AUTN et de la cle publique du HSS
		String sipAuthenticate = HexString.bufferToHex(_sipAuthenticateB) + "," + pubClient;
		_sipAuthenticate = sipAuthenticate.getBytes();
		
		if (__log.isDebugEnabled()) {
			__log.debug("Random " + HexString.bufferToHex(_rand));
			__log.debug("OP " + HexString.bufferToHex(operatorId));
			__log.debug("OPC " + HexString.bufferToHex(_opC));
			__log.debug("MAC " + HexString.bufferToHex(mac));
			__log.debug("XRES " + HexString.bufferToHex(_xresA));
			__log.debug("CK " + HexString.bufferToHex(_ckA));
			__log.debug("AUTN "+ HexString.bufferToHex(_sipAuthenticateA));
			__log.debug("SQN " + HexString.bufferToHex(sqn));
		}
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}
	
	private byte[] encrypt(byte[] autn, SecretKey k){
		Cipher c = null;
		try {
			c = Cipher.getInstance("DES");
			c.init(Cipher.ENCRYPT_MODE, k);

			return c.doFinal(autn);
		} catch (NoSuchAlgorithmException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (NoSuchPaddingException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		} catch (InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	
	protected static byte[] computeSipAuthenticate(byte[] sqn, byte[] amf, byte[] mac, byte[] ak) throws UnsupportedEncodingException
	{
		byte[] bNonce = new byte[sqn.length + amf.length + mac.length];
		for (int i = 0; i < bNonce.length; i++)
		{
			if (i < sqn.length) {
				bNonce[i] = (byte) (sqn[i] ^ ak[i]);
			} else if (i < (sqn.length + amf.length)) {
				bNonce[i] = amf[i - sqn.length];
			} else {
				bNonce[i] = mac[i - sqn.length - amf.length];
			}
		}
		return bNonce;
	}
		
	public byte[] getRand() {
		return this._rand;
	}
	public byte[] getSipAuthenticate() {
		return _sipAuthenticate;
	}
	public byte[] getXres() {
		return this._xres;
	}
	public byte[] getCk() {
		return this._ck;
	}
	public byte[] getIk() {
		return this._ik;
	} 
	public byte[] getAkStar(byte[] k) throws InvalidKeyException, ArrayIndexOutOfBoundsException {
		return Milenage.f5star(k, _rand, _opC);
	}
	public byte[] getMacs(byte[] sqn, byte[] k) throws InvalidKeyException, ArrayIndexOutOfBoundsException {
		return Milenage.f1star(k, _rand, _opC, sqn, __amf);
	}
	public static byte[] getAmf()
	{
		return __amf;
	}

	public AVP<AVPList> asAuthItem() {
		// Remplir les champs de la reponse
		AVPList avps = new AVPList();
		avps.add(Cx.SIP_AUTHENTICATION_SCHEME, this.scheme);
		avps.add(Cx.SIP_AUTHENTICATE, _sipAuthenticate);
		avps.add(Cx.SIP_AUTHORIZATION, _xres);
		avps.add(Cx.CONFIDENTIALITY_KEY, _ck);
		avps.add(Cx.INTEGRITY_KEY, _ik);
		return new AVP<AVPList>(Cx.SIP_AUTH_DATA_ITEM, avps);
	}
	
}
